package org.hzero.interfaces.common.helper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.hzero.interfaces.common.concurrent.KnifeInterfaceRecordThread;
import org.hzero.interfaces.common.concurrent.KnifeInterfaceRecordThreadPool;
import org.hzero.interfaces.common.entity.KnifeFile;
import org.hzero.interfaces.common.entity.KnifeInterfaceLogRecord;
import org.hzero.interfaces.common.utils.KnifeLogParamUtil;
import org.hzero.interfaces.common.utils.file.KnifeFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author: tianhao.luo@hand-china.com 2021/7/1  上午11:58
 */
public abstract class KnifeInterfaceLogHelper {
    public static final String ALIAS_RESPONSE_CONTENT = "response-content";
    public static final String ALIAS_REQUEST_BODY = "request-body";
    public static final String ALIAS_REQUEST_URL = "request-url";
    public static final Integer BIG_STRING_LENGTH = Integer.MAX_VALUE;
    private static final Logger LOGGER = LoggerFactory.getLogger(KnifeInterfaceLogHelper.class);
    /**
     * 线程池
     */
    public final ThreadPoolExecutor interfaceRecodeThreadPool;
    public final KnifeInterfaceLogRecord knifeInterfaceLogRecord;
    public final ObjectMapper objectMapper = new ObjectMapper();
    public Exception exception;

    /**
     * requestBody参数的名称
     */
    public volatile String requestBodyObjName;

    {
        this.interfaceRecodeThreadPool = KnifeInterfaceRecordThreadPool.getInterfaceRecodeThreadPool();
        // 将Date类型的属性，如果没有添加JSONFormat注解的，默认按照"yyyy-MM-dd HH:mm:ss"进行序列化
        SimpleModule module = new SimpleModule();
        objectMapper.registerModule(module);
    }

    public KnifeInterfaceLogHelper(KnifeInterfaceLogRecord knifeInterfaceLogRecord) {
        this.knifeInterfaceLogRecord = knifeInterfaceLogRecord;

        // 若未定义初始化时间初始化请求时间
        if (this.knifeInterfaceLogRecord.getInterfaceRequestTime() == null) {
            this.knifeInterfaceLogRecord.setInterfaceRequestTime(new Date());
        }
    }

    public KnifeInterfaceLogHelper(KnifeInterfaceLogRecord knifeInterfaceLogRecord, Exception exception) {
        Assert.notNull(exception, "不可以传入空的exception");
        this.knifeInterfaceLogRecord = knifeInterfaceLogRecord;
        this.exception = exception;
        // 若未定义初始化时间初始化请求时间
        if (this.knifeInterfaceLogRecord.getInterfaceRequestTime() == null) {
            this.knifeInterfaceLogRecord.setInterfaceRequestTime(new Date());
        }
    }

    /**
     * 预处理
     *
     * @param method 方法
     */
    public void preDeal(Method method) throws UnknownHostException {
        // 1.设置请求的key
        UUID uuid = UUID.randomUUID();
        knifeInterfaceLogRecord.setInvokeKey(uuid.toString());
        InetAddress localHost = InetAddress.getLocalHost();
        String hostAddress = localHost.getHostAddress();
        knifeInterfaceLogRecord.setIp(hostAddress);
        knifeInterfaceLogRecord.setRequestMethod(method.getName());

        // 2 找是否有requestBody注解
        findRequestBodyObjName(method);
    }


    /**
     * 获取requestBody参数位置
     *
     * @param method 代理方法
     */
    public void findRequestBodyObjName(Method method) {
        // 2.还需要找出标记了RequestBody的方法
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        // 一个post请求只能有一个RequestBody
        if (parameterAnnotations.length > 0) {
            for (int i = 0; i < parameterAnnotations.length; i++) {
                for (int j = 0; j < parameterAnnotations[i].length; j++) {
                    if (parameterAnnotations[i][j].annotationType().equals(RequestBody.class)) {
                        // 获取requestBody索引
                        Parameter[] parameters = method.getParameters();
                        requestBodyObjName = parameters[i].getName();
                        break;
                    }
                }
            }
        }
    }

    /**
     * 处理返回结果
     *
     * @param responseObj      响应体
     * @param ignoreClassArray 不被日志记录的类型参数
     */
    public void buildInterfaceLogResponse(Object responseObj, Class<?>... ignoreClassArray) {
        // 2.更新响应时间
        Date end = new Date();
        knifeInterfaceLogRecord.setInterfaceResponseTime(end);
        // 3.持续时间
        knifeInterfaceLogRecord.setRequestTime(end.getTime() - knifeInterfaceLogRecord.getInterfaceRequestTime().getTime());
        // 4.处理响应体
        setRespContentOrFileId(responseObj, ignoreClassArray);
        // 如果aop捕获异常,并传入
        setRespContentOfException();
    }

    /**
     * 处理请求体
     *
     * @param args             参数
     * @param ignoreClassArray 不被日志记录的类型参数
     */
    public abstract void buildInterfaceLogRequest(Object[] args, Class<?>... ignoreClassArray);

    /**
     * 处理请求提
     *
     * @param map              参数
     * @param ignoreClassArray 不被日志记录的类型参数
     */
    public abstract void buildInterfaceLogRequest(Map<String, Object> map, Class<?>... ignoreClassArray);

    /**
     * 持久化日志
     */
    public void enduranceHitfInterfaceLog() {
        KnifeInterfaceRecordThread knifeInterfaceRecordThread = new KnifeInterfaceRecordThread(knifeInterfaceLogRecord);
        interfaceRecodeThreadPool.execute(knifeInterfaceRecordThread);
    }


    /**
     * 上传超长的log日志
     *
     * @param bytes     字节数组
     * @param aliasName 被写入的文件别名
     * @param invokeKey 调用的key
     * @return 文件信息
     */
    public KnifeFile uploadLogFile(byte[] bytes, String aliasName, String invokeKey) {
        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);) {
            return new KnifeFileUpload() {
                @Override
                public KnifeFile upload(InputStream inputStream) {
                    // todo 上传文件

                    return new KnifeFile();
                }
            }.upload(byteArrayInputStream);
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
            // 避免空指针
            return new KnifeFile();
        }
    }

    /**
     * 截断length的字符串
     *
     * @param bytes  待切割字符
     * @param length 指定长度
     * @return 切割好的字符串
     */
    public String cutOffString(byte[] bytes, int length) {
        // 将字符串转化为utf-8字节数组
        // 根据字节数组来判断作何处理
        int strLength = bytes.length;
        if (strLength > length) {
            // copy指定length长度字符
            byte[] bytes2 = Arrays.copyOf(bytes, length);
            // 将字节转化为字符串
            String firstCutStr = new String(bytes2, StandardCharsets.UTF_8);
            // 存在最后一个字节不是英文（如中文）的情况，如果切分出的剩余字节无法组成一个汉字
            // 就会出现�乱码，所以要把最后那个乱码解决
            if (firstCutStr.getBytes(StandardCharsets.UTF_8).length > length) {
                return firstCutStr.replace("�", "");
            } else {
                return firstCutStr;
            }
        } else {
            return new String(bytes, StandardCharsets.UTF_8);
        }
    }

    /**
     * 判断参数的Class是否在ignoreClassArray中,通过缓存
     *
     * @param objClass         参数的Class
     * @param ignoreClassArray 忽略列表
     * @return 被忽略则返回true;否则返回false
     */
    public boolean argIsIgnore(Class<?> objClass, Class<?>... ignoreClassArray) {
        return KnifeLogParamUtil.argIsIgnore(objClass, ignoreClassArray);
    }

    /**
     * 记录响应结果
     *
     * @param responseObj 响应结果
     */
    public void setRespContentOrFileId(Object responseObj, Class<?>... ignoreClassArray) {
        if (responseObj != null && !argIsIgnore(responseObj.getClass(), ignoreClassArray)) {
            String responseStr;
            try {
                responseStr = objectMapper.writeValueAsString(responseObj);
            } catch (JsonProcessingException jsonProcessingException) {
                responseStr = responseObj.toString();
            }
            byte[] bytes = responseStr.getBytes(StandardCharsets.UTF_8);
            if (bytes.length > BIG_STRING_LENGTH) {
                KnifeFile knifeFile = uploadLogFile(bytes, ALIAS_RESPONSE_CONTENT, knifeInterfaceLogRecord.getInvokeKey());
                knifeInterfaceLogRecord.setInterfaceRespContentFileId(Optional.ofNullable(knifeFile.getId()).orElse(null));
            } else {
                knifeInterfaceLogRecord.setInterfaceRespContent(responseStr);
            }
        }
    }

    /**
     * 记录异常的响应信息
     */
    public void setRespContentOfException() {
        if (exception != null) {
            String exceptionStr;
            try {
                exceptionStr = objectMapper.writeValueAsString(exception.getMessage());
            } catch (JsonProcessingException jsonProcessingException) {
                exceptionStr = exception.toString();
            }
            // 4.处理响应体
            byte[] bytes = exceptionStr.getBytes(StandardCharsets.UTF_8);
            if (bytes.length > BIG_STRING_LENGTH) {
                KnifeFile knifeFile = uploadLogFile(bytes, ALIAS_RESPONSE_CONTENT, knifeInterfaceLogRecord.getInvokeKey());
                knifeInterfaceLogRecord.setInterfaceRespContentFileId(Optional.ofNullable(knifeFile.getId()).orElse(null));
            } else {
                knifeInterfaceLogRecord.setInterfaceRespContent(exceptionStr);
            }
        }
    }

    /**
     * 将参数作为日志记录的响应结果
     *
     * @param requestBodyObj 响应结果
     */
    public void setRequestBodyContentOrFileId(Object requestBodyObj) {
        String requestBodyStr;
        try {
            requestBodyStr = objectMapper.writeValueAsString(requestBodyObj);
        } catch (JsonProcessingException jsonProcessingException) {
            requestBodyStr = requestBodyObj.toString();
        }
        byte[] bytes = requestBodyStr.getBytes(StandardCharsets.UTF_8);
        if (bytes.length > BIG_STRING_LENGTH) {
            KnifeFile knifeFile = uploadLogFile(bytes, ALIAS_REQUEST_BODY, knifeInterfaceLogRecord.getInvokeKey());
            knifeInterfaceLogRecord.setInterfaceReqBodyParamFileId(Optional.ofNullable(knifeFile.getId()).orElse(null));
        } else {
            knifeInterfaceLogRecord.setInterfaceReqBodyParam(requestBodyStr);
        }
    }

    /**
     * 将参数作为日志记录的url参数
     *
     * @param requestUrlObj 路径参数
     */
    public void setRequestUrlContentOrFileId(Object requestUrlObj) {
        // 所有参数都属于路径参数
        String requestUrlStr;
        try {
            requestUrlStr = objectMapper.writeValueAsString(requestUrlObj);
        } catch (JsonProcessingException jsonProcessingException) {
            requestUrlStr = requestUrlObj.toString();
        }
        byte[] bytes = requestUrlStr.getBytes(StandardCharsets.UTF_8);
        if (bytes.length > BIG_STRING_LENGTH) {
            KnifeFile knifeFile = uploadLogFile(bytes, ALIAS_REQUEST_BODY, knifeInterfaceLogRecord.getInvokeKey());
            knifeInterfaceLogRecord.setInterfaceReqBodyParamFileId(Optional.ofNullable(knifeFile.getId()).orElse(null));
        } else {
            knifeInterfaceLogRecord.setInterfaceReqUrlParam(requestUrlStr);
        }
    }


    /**
     * 记录本次请求的http头信息
     *
     * @param headers 头信息
     */
    public void setRequestHeaderOrFileId(Object headers) {
        String interfaceReqHeaderParam = "";
        if (headers != null) {
            try {
                interfaceReqHeaderParam = objectMapper.writeValueAsString(headers);
            } catch (JsonProcessingException e) {
                LOGGER.error(e.getMessage());
                interfaceReqHeaderParam = headers.toString();
            }
        }
        knifeInterfaceLogRecord.setInterfaceReqHeaderParam(cutOffString(interfaceReqHeaderParam.getBytes(StandardCharsets.UTF_8), BIG_STRING_LENGTH));
    }
}
